
package physics;

import ai_game.AIGame;
import entity.GameObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Circle;
import org.newdawn.slick.geom.GeomUtil;
import org.newdawn.slick.geom.Line;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;

/**
 *
 * @author ant
 * 
 * Handle the interaction of objects in 2D space. One instance of this object
 * will handle the physics for a loading area.
 */
public class PhysicsEngine
{
    private float intervalLength;
    
    //raycast requests made since the last render. will be drawn when in debug
    //mode
    private ArrayList<Line> raycastRequests = new ArrayList<>();
        
    public PhysicsEngine(float tickLength)
    {
        intervalLength = tickLength;
    }
    
    public void update()
    {
        Iterator<GameObject> goIter = gameObjects.iterator();
        
        while(goIter.hasNext())
        {
            GameObject crntObj = goIter.next();
            if(crntObj.expired)
                goIter.remove();
            
            move(crntObj);
        }
        
        checkCollision();
    }
            
    ArrayList<GameObject> gameObjects = new ArrayList<>();
    GeomUtil geomUtil = new GeomUtil();
    
    
        
    public void addObject(GameObject p)
    {
        gameObjects.add(p);
    }
    
    //default to not print debug simply call with parameter false
    public double[] pieSliceQuadSensor(Vector2f pos, float radius, float angleRadians, CollisionClass targets)
    {
        return pieSliceQuadSensor( pos, radius,  angleRadians,targets, false);
    }

    public double[] pieSliceQuadSensor(Vector2f pos, float radius, float angleRadians, CollisionClass targets, boolean debug)
    {
//        System.out.println(angleRadians);
        List<GameObject> results = getWithinRadius(pos, radius, targets);
        double[] activationLevels = new double[4];
       
        for(GameObject obj : results)
        {
            double angleToObj = Math.atan2(obj.pos.x - pos.x, obj.pos.y - pos.y)+Math.PI/2;
            
            int quadrant=0;
            
            
            double relang = angleRadians+angleToObj;  //angle of object relative to player...
            if(relang>Math.PI*2) relang-= Math.PI*2;
            if(relang<0) relang+= Math.PI*2;
            
            if(debug){
                 System.out.println(relang);
            //    System.out.printf("angle to obj: %f player angle: %f  relative angle %f\n", angleToObj, angleRadians, relang);
            } 
             
            if (relang>Math.toRadians(45) && relang<Math.toRadians(135)) quadrant=2;
            else if (relang>Math.toRadians(135) && relang<Math.toRadians(225)) quadrant=0;
            else if (relang>Math.toRadians(225) && relang<Math.toRadians(315)) quadrant=1;
            else quadrant=3;
            
            Vector2f displacement = obj.pos.copy();
            displacement.sub(pos);
            
            activationLevels[quadrant] += 1/displacement.lengthSquared();
        }

        double max = 0;
        for(int i=0;i<4; ++i)
        {
            if(activationLevels[i] > max) max = activationLevels[i];
        }
        if(max > 1)
        {
            for(int i=0;i<4; ++i)
            {
                activationLevels[i] /= max;
            }
        }
        return activationLevels;
    }
    
    /**
     * 
     * @param pos position to center the circle
     * @param radius radius of the circle
     * @param collisionClass if not null, only find objects of this type
     * @return list of objects whose collision check intersects with the circle
     */
    public List<GameObject> getWithinRadius(Vector2f pos, float radius, CollisionClass collisionClass)
    {
        
        ArrayList<GameObject> objects = new ArrayList<>();
        Circle circle = new Circle(pos.x, pos.y, radius);
                
        for(GameObject go : gameObjects)
        {
            if(collisionClass == null || collisionClass == go.collisionClass)
            {
                if(go.collisionShape != null && go.collisionShape.intersects(circle))
                {
                    objects.add(go);
                }                
            }
        }
        return objects;
    }
    
    /**
     * get the first object in the way of the ray, or null if there is no such
     * object
     * @param pos position from which to cast
     * @param angleRadians 
     * @param length  
     * @param exclusionCharacter if specified, this game object will not be considered
     * for collision. e.g. the object performing the ray cast.
     */
    public RaycastResult rayCast(Vector2f pos, double angleRadians, double length, GameObject excluded)
    {
        float dist_x = (float) ((float) Math.cos(angleRadians)*length);
        float dist_y = (float) ((float) Math.sin(angleRadians)*length);
        
        RaycastResult rr = new RaycastResult();
        rr.distance = length;

        Line segment = new Line(pos.x, pos.y, pos.x+dist_x, pos.y + dist_y);
        
        if(AIGame.debug)
        {
            raycastRequests.add(segment);
        }
        
        
        GameObject closest = null;
        double distSq = length*length;
            
        for(GameObject obj : gameObjects)
        {
            if(excluded == obj)
                continue;
            
            GeomUtil.HitResult result = geomUtil.intersect(obj.collisionShape,segment);
            if(result != null)
            {
                double crntDistSq = result.pt.copy().sub(pos).lengthSquared();
                
                if(crntDistSq < distSq)
                {
                    rr.object = obj;
                    rr.distance = Math.sqrt(crntDistSq);
                }
            }
            
        }
        return rr;
    }
    
    private double dist(Vector2f a, Vector2f b){
        double x = a.x - b.x;
        double y = a.y - b.y;
        return Math.sqrt(x*x+y*y);
     }
    
            
    private void move(GameObject go)
    {
        if(go.acc != null)
        {
            go.vel.add(go.acc.copy().scale(intervalLength));
        }
                
        go.pos.add(go.vel);
        
        if(go.collisionShape != null)
        {
            go.collisionShape.setCenterX(go.pos.x);
            go.collisionShape.setCenterY(go.pos.y);
        }
    }

    public void renderDebugShapes(Rectangle camera, Graphics g)
    {
        g.pushTransform();
        g.translate(-camera.getMinX(), -camera.getMinY());

        for(GameObject go : gameObjects)
        {
            if(go.collisionShape != null && go.collisionShape.intersects(camera))
            {
                g.draw(go.collisionShape);
            }
        }
        
        for(Line l : raycastRequests)
        {
                g.draw(l);
//                g.drawLine(0,0,1000,1000);
                //camera is offscreen
         //       System.out.println(l);
//            g.drawLine(l.getX1(), l.getY1(), l.getX2(), l.getY2());
        }
        raycastRequests.clear();
        g.popTransform();    
    }
    
    public void checkCollision()
    {
        for(int i=0; i < gameObjects.size(); ++i)
        {
            for(int j=i+1; j < gameObjects.size(); ++j)
            {
                GameObject go1 = gameObjects.get(i), go2 = gameObjects.get(j);
                
                if(go1.collisionShape != null && go2.collisionShape != null &&
                   go1.collisionClass != go2.collisionClass &&
                   go1.collisionShape.intersects(go2.collisionShape))
                {
                    go1.collide(go2);
                    go2.collide(go1);
                }
            }
        }
    }
}
